package com.hongji.lms5xxcommunicator.scanner.imp;

import com.hongji.lms5xxcommunicator.realtime.sync.XSource;
import com.hongji.lms5xxcommunicator.scanner.ScanFrame;
import com.hongji.lms5xxcommunicator.scanner.ScanStrategy;
import lombok.extern.slf4j.Slf4j;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessageUnpacker;
import org.msgpack.value.ArrayValue;
import org.msgpack.value.ValueType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.CRC32;

@Slf4j
@Service
public class Pics150ScannerStrategy implements ScanStrategy {

    // 监听配置
    @Value("${scanner.PICS150.listen_ip:0.0.0.0}") private String listenIp;
    @Value("${scanner.PICS150.listen_port}") private int listenPort;
    @Value("${scanner.PICS150.expect_stx:true}") private boolean expectStxHeader;

    // 几何映射（Y/Z）
    @Value("${scanner.PICS150.geo.channel:83}") private int geoChannelCode;
    @Value("${scanner.PICS150.geo.y0_mm:0}")    private double y0mm;
    @Value("${scanner.PICS150.geo.dy_mm:5}")    private double dymm;
    @Value("${scanner.PICS150.geo.z_scale_mm:1.0}") private double zScale;
    @Value("${scanner.PICS150.geo.z_offset_mm:0.0}") private double zOffset;

    // —— X 最近邻容差（ms）——
    @Value("${x.lookup.tolerance.ms:30}") private int xLookupTolMs;

    // 注入 XSource（内部已做 OPC 采样 + 坐标统一）
    @Autowired private XSource xSource;

    private final AtomicBoolean running = new AtomicBoolean(false);
    private DatagramSocket sock;
    private ExecutorService udpExec;
    private volatile ScanFrameListener listener;

    private volatile double lastX = 0.0;

    @Override
    public void start(ScanFrameListener listener) throws Exception {
        if (!running.compareAndSet(false, true)) return;
        this.listener = listener;

        // 开启 XSource 采样
        xSource.start();

        sock = new DatagramSocket(null);
        sock.setReuseAddress(true);
        sock.bind(new InetSocketAddress(listenIp, listenPort));

        udpExec = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "pics150-udp"); t.setDaemon(true); return t;
        });
        udpExec.submit(this::udpLoop);

        log.info("[PICS150] listening UDP on {}:{}", listenIp, listenPort);
    }

    @Override
    public void stop() {
        if (!running.getAndSet(false)) return;
        try { if (sock != null) sock.close(); } catch (Exception ignore) {}
        if (udpExec != null) udpExec.shutdownNow();

        // 关闭 XSource 采样
        try { xSource.stop(); } catch (Exception ignore) {}

        log.info("[PICS150] stopped");
    }

    @Override
    public boolean isRunning() { return running.get(); }

    private void udpLoop() {
        byte[] buf = new byte[65535];
        DatagramPacket p = new DatagramPacket(buf, buf.length);

        while (running.get()) {
            try {
                sock.receive(p);
                int off = p.getOffset();
                int len = p.getLength();

                int stx = expectStxHeader ? find4xSTX(buf, off, len) : off;
                if (stx < 0) continue;

                ByteBuffer bb = ByteBuffer.wrap(buf, stx, len - (stx - off)).order(ByteOrder.LITTLE_ENDIAN);

                if (expectStxHeader) {
                    if (bb.remaining() < 12) continue;
                    bb.get(); bb.get(); bb.get(); bb.get(); // 4×STX
                }

                if (bb.remaining() < 8) continue;
                int payLen = bb.getInt();
                if (payLen < 0 || bb.remaining() < payLen + 4) continue;

                byte[] payload = new byte[payLen];
                bb.get(payload);
                long crcRecv = Integer.toUnsignedLong(bb.getInt());

                CRC32 c32 = new CRC32();
                c32.update(payload);
                if (c32.getValue() != crcRecv) continue;

                long hostNs = System.nanoTime();

                try (MessageUnpacker up = MessagePack.newDefaultUnpacker(payload)) {
                    org.msgpack.value.Value root = up.unpackValue();
                    Parsed pic = parsePicsFrame(root);
                    if (pic == null || pic.zRaw == null || pic.zRaw.length == 0) continue;

                    int n = pic.zRaw.length;
                    double[] ys = new double[n];
                    double[] zs = new double[n];
                    for (int i = 0; i < n; i++) {
                        ys[i] = y0mm + i * dymm;
                        zs[i] = pic.zRaw[i] * zScale + zOffset;
                    }

                    // —— 在策略内使用 XSource 做最近邻 —— //
                    final long tolNs = Math.max(1, xLookupTolMs) * 1_000_000L;
                    double xPoint = xSource.nearestAtHostNs(hostNs, tolNs, lastX);
                    lastX = xPoint;

                    double[] xs = new double[n];
                    Arrays.fill(xs, xPoint);

                    Long deviceUs = (pic.tSendUs != null) ? pic.tSendUs : pic.tSampleUs; // 114 优先
                    if (listener != null) listener.onFrame(new ScanFrame(hostNs, deviceUs, xs, ys, zs));
                }
            } catch (Exception e) {
                if (running.get()) log.warn("[PICS150] UDP error: {}", e.toString());
            }
        }
    }

    private static int find4xSTX(byte[] a, int off, int len) {
        int end = off + len - 4;
        for (int i = off; i <= end; i++) {
            if (a[i] == 0x02 && a[i+1] == 0x02 && a[i+2] == 0x02 && a[i+3] == 0x02) return i;
        }
        return -1;
    }

    private static final class Parsed {
        Long tSampleUs;  // 113
        Long tSendUs;    // 114
        int  points;     // 119 或通道18
        double[] zRaw;   // 选中通道数据
    }

    private Parsed parsePicsFrame(org.msgpack.value.Value root) {
        if (root.getValueType() != ValueType.MAP) return null;

        Map<org.msgpack.value.Value, org.msgpack.value.Value> top = root.asMapValue().map();

        // 顶层 "17"
        org.msgpack.value.Value block17 = getChild(top, "17");
        if (block17 == null || block17.getValueType() != ValueType.MAP) return null;

        Parsed out = new Parsed();

        Map<org.msgpack.value.Value, org.msgpack.value.Value> m17 = block17.asMapValue().map();

        // "150": [...]
        org.msgpack.value.Value v150 = getChild(m17, "150");
        if (v150 == null || v150.getValueType() != ValueType.ARRAY) return null;
        ArrayValue arr150 = v150.asArrayValue();

        // 在 150[0].17 中取 113/114/119
        if (arr150.size() > 0) {
            org.msgpack.value.Value first = arr150.get(0);
            if (first.getValueType() == ValueType.MAP) {
                Map<org.msgpack.value.Value, org.msgpack.value.Value> m = first.asMapValue().map();
                org.msgpack.value.Value inner17 = getChild(m, "17");
                if (inner17 != null && inner17.getValueType() == ValueType.MAP) {
                    Map<org.msgpack.value.Value, org.msgpack.value.Value> mi = inner17.asMapValue().map();
                    out.tSampleUs = asLong(getChild(mi, "113"));
                    out.tSendUs   = asLong(getChild(mi, "114"));
                    Integer p = asInt(getChild(mi, "119"));
                    if (p != null) out.points = p.intValue();
                }
            }
        }

        // 选择通道 geoChannelCode，解 17(bin)
        double[] chosen = null; Integer chosenPoints = null;
        for (int i = 0; i < arr150.size(); i++) {
            org.msgpack.value.Value blk = arr150.get(i);
            if (blk.getValueType() != ValueType.MAP) continue;

            Map<org.msgpack.value.Value, org.msgpack.value.Value> mm = blk.asMapValue().map();
            org.msgpack.value.Value chanArr = getChild(mm, String.valueOf(geoChannelCode));
            if (chanArr == null || chanArr.getValueType() != ValueType.ARRAY) continue;

            ArrayValue entries = chanArr.asArrayValue();
            for (int k = 0; k < entries.size(); k++) {
                org.msgpack.value.Value ent = entries.get(k);
                if (ent.getValueType() != ValueType.MAP) continue;

                Map<org.msgpack.value.Value, org.msgpack.value.Value> me = ent.asMapValue().map();
                Integer cnt = asInt(getChild(me, "18"));

                // A) 离线 NDJSON：17 -> {"_bin_b64":"..."}
                org.msgpack.value.Value binNode = getChild(me, "17");
                if (binNode != null && binNode.getValueType() == ValueType.MAP) {
                    Map<org.msgpack.value.Value, org.msgpack.value.Value> mb = binNode.asMapValue().map();
                    org.msgpack.value.Value binVal = getChild(mb, "_bin_b64");
                    if (binVal != null && binVal.getValueType() == ValueType.STRING) {
                        byte[] raw = Base64.getDecoder().decode(binVal.asStringValue().asString());
                        double[] dbl = u16leToDouble(raw, cnt);
                        chosen = dbl; chosenPoints = (cnt != null ? cnt : Integer.valueOf(dbl.length));
                        break;
                    }
                }

                // B) 在线 UDP：17 为 BinaryValue
                org.msgpack.value.Value directBin = getChild(me, "17");
                if (directBin != null && directBin.getValueType() == ValueType.BINARY) {
                    byte[] raw = directBin.asBinaryValue().asByteArray();
                    double[] dbl = u16leToDouble(raw, cnt);
                    chosen = dbl; chosenPoints = (cnt != null ? cnt : Integer.valueOf(dbl.length));
                    break;
                }
            }
            if (chosen != null) break;
        }

        if (chosen == null) return null;
        out.zRaw = chosen;
        if (out.points == 0 && chosenPoints != null) out.points = chosenPoints.intValue();
        return out;
    }

    private static org.msgpack.value.Value getChild(
            Map<org.msgpack.value.Value, org.msgpack.value.Value> map, String key) {
        for (Map.Entry<org.msgpack.value.Value, org.msgpack.value.Value> e : map.entrySet()) {
            if (isKey(e.getKey(), key)) return e.getValue();
        }
        return null;
    }

    private static boolean isKey(org.msgpack.value.Value v, String key) {
        return (v.getValueType() == ValueType.STRING  && key.equals(v.asStringValue().asString()))
                || (v.getValueType() == ValueType.INTEGER && key.equals(String.valueOf(v.asIntegerValue().toInt())));
    }

    private static Integer asInt(org.msgpack.value.Value v) {
        try { return (v != null && v.getValueType() == ValueType.INTEGER) ? v.asIntegerValue().toInt() : null; }
        catch (Exception e) { return null; }
    }

    private static Long asLong(org.msgpack.value.Value v) {
        try { return (v != null && v.getValueType() == ValueType.INTEGER) ? v.asIntegerValue().toLong() : null; }
        catch (Exception e) { return null; }
    }

    private static double[] u16leToDouble(byte[] raw, Integer expectCount) {
        int n = raw.length >>> 1;
        if (expectCount != null) n = Math.min(n, expectCount.intValue());
        double[] out = new double[n];
        ByteBuffer bb = ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);
        for (int i = 0; i < n; i++) out[i] = (double) (bb.getShort() & 0xFFFF);
        return out;
    }
}
